package com.example.selfservicestation.util

import android.util.Log
import com.example.selfservicestation.event.LoggedEvent
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.io.IOException
import java.io.PrintWriter
import java.io.RandomAccessFile
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit

object Logger {


    private val logs = LinkedBlockingQueue<LogMsg>()

    private const val CRASH_LOG_DIR = "crash-logs"
    private const val ERROR_LOG_DIR = "error-logs"
    private const val INFO_LOG_DIR = "info-logs"

    const val LAVEL_D = "D"
    const val LAVEL_I = "I"
    const val LAVEL_W = "W"
    const val LAVEL_E = "E"
    const val LAVEL_C = "C"

    // 最后一次检查时间
    private var lastChecked = 0L

    private val thread = Thread {
        while (true) {
            process(logs.take())
            checkFiles()
        }
    }

    init {
        thread.isDaemon = true
        thread.start()
    }

    fun d(tag: String, content: String) {
        log(LAVEL_D, tag, content)
    }

    fun i(tag: String, content: String) {
        Log.i(tag, content)
        log(LAVEL_I, tag, content)
    }

    fun w(tag: String, content: String) {
        Log.w(tag, content)
        log(LAVEL_W, tag, content)
    }

    fun e(tag: String, content: String, e: Throwable? = null) {
        Log.e(tag, content, e)
        log(LAVEL_E, tag, content, e)
    }

    fun c(tag: String, content: String, e: Throwable? = null) {
        Log.e(tag, content, e)
        log(LAVEL_C, tag, content, e)
    }

    private fun log(level: String, tag: String, content: String, e: Throwable? = null) {
        logs.add(toLogMsg(level, tag, content, e))
    }

    private fun toLogMsg(level: String, tag: String, content: String, e: Throwable? = null) : LogMsg {
        val dateFormat = SimpleDateFormat("yyy-MM-dd", Locale.ROOT)
        val timeFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.ROOT)
        val now = Date(System.currentTimeMillis())
        return LogMsg(dateFormat.format(now), timeFormat.format(now), level, tag, content, e)
    }

    private fun process(msg: LogMsg) {
        val log = msg.toString()
        if (msg.lavel == LAVEL_C) {
            writeToFile(CRASH_LOG_DIR, log, msg.date)
        }
        if (msg.lavel == LAVEL_E) {
            writeToFile(ERROR_LOG_DIR, log, msg.date)
        }
        if (msg.lavel != LAVEL_D) {
            writeToFile(INFO_LOG_DIR, log, msg.date)
        }
        EventBus.getDefault().post(LoggedEvent(log))
    }

    private fun writeToFile(dir: String, log: String, date: String) {
        val file = extFile(dir + File.separator + date)
        if (!file.exists()) {
            try {
                val parent = file.parentFile
                while (parent?.exists() == false) {
                    if (!parent.mkdirs()) {
                        // 当用户还没来得及给文件读写权限的时候，创建目录会返回 false
                        // 此时，等待 10 毫秒，再去尝试创建
                        Thread.sleep(10)
                    }
                }
                while (!file.createNewFile()) {
                    // 当用户还没来得及给文件读写权限的时候，创建目录会返回 false
                    // 此时，等待 10 毫秒，再去尝试创建
                    Thread.sleep(10)
                }
            } catch (e: IOException) {
                Log.e("Logger", "Cannot create file [${file.absolutePath}]", e)
                val elog = toLogMsg(LAVEL_E, "Logger", "Cannot create file [${file.absolutePath}]", e)
                EventBus.getDefault().post(LoggedEvent(elog.toString()))
                return
            }
        }
        while (true) {
            try {
                val raf = RandomAccessFile(file, "rw")
                raf.seek(raf.length())
                raf.write(log.toByteArray())
                raf.writeBytes("\n")
                raf.closeQuietly()
                break
            } catch (e: Exception) {
                Log.e("Logger", "Cannot write file [${file.absolutePath}]", e)
                val elog = toLogMsg(LAVEL_E, "Logger", "Cannot write file [${file.absolutePath}]", e)
                EventBus.getDefault().post(LoggedEvent(elog.toString()))
                Thread.sleep(10)
            }
        }
    }

    private fun checkFiles() {
        val now = jvmSeconds()
        if (now - lastChecked > TimeUnit.DAYS.toSeconds(1)) {
            // 1 天检查一次，删除过旧的日志
            checkDelete(CRASH_LOG_DIR, 30)
            checkDelete(ERROR_LOG_DIR, 10)
            checkDelete(INFO_LOG_DIR, 5)
            lastChecked = now
        }
    }

    private fun checkDelete(dirPath: String, maxFiles: Int) {
        val dirFile = extFile(dirPath)
        if (!dirFile.exists() || !dirFile.isDirectory) {
            return
        }
        dirFile.listFiles()?.let { files ->
            files.sortBy { it.name }
            for (i in files.indices) {
                if (i < files.size - maxFiles) {
                    val f = files[i]
                    i("Logger", "删除日志 ${dirPath}/${f.name}")
                    f.delete()
                }
            }
        }
    }

    fun todayLog() : File {
        val dateFormat = SimpleDateFormat("yyy-MM-dd", Locale.ROOT)
        val today = dateFormat.format(Date(System.currentTimeMillis()))
        return extFile(INFO_LOG_DIR + File.separator + today)
    }

    class LogMsg(
        val date: String,
        val time: String,
        val lavel: String,
        val tag: String,
        val content: String,
        val e: Throwable? = null
    ) {

        override fun toString(): String {
            val writer = StringWriter()
            writer.write(time)
            writer.write(" ")
            writer.write(lavel)
            writer.write("/")
            writer.write(tag)
            writer.write(": ")
            writer.write(content)
            e?.printStackTrace(PrintWriter(writer))
            return writer.toString()
        }

    }

}